
import Patch;
import TriPool;
import Renderer;
import Gpgpu;
static import Perlin;

import std.stdio;
import std.c.string;
import std.c.stdlib:malloc;
import std.math:sqrt;
import maths.Misc:pi;
import maths.Vec;


class Landscape{
	this(int _mapSize, int pps, bool _gpgpuE){
		gpgpuEnabled = _gpgpuE;
		mapSize = _mapSize;
		patchesPerSide = pps;
		triPool = new TriPool();
		loadTerrain();
		patchSize = mapSize / patchesPerSide;
		varianceDepth = cast(int)(sqrt(cast(float)patchSize)) + 1;
//		assert(patchesPerSide == 16);
		patches.length = patchesPerSide;
		foreach(inout p; patches){
			p.length = patchesPerSide;
		}

		Patch patch;

		for(int y = 0; y < patchesPerSide; y++)
			for(int x = 0; x < patchesPerSide; x++){
				//patch = &patches[y][x];
				patch = new Patch(x*patchSize, y*patchSize, x*patchSize, y*patchSize, mapSize, patchSize, varianceDepth, heightMap, triPool);
				patch.computeVariance();
				patches[y][x] = patch;
			}

		viewPosition = vec3(512.f, 5., 512.f);
	}

	void reset(){
		int eyeX = cast(int)(viewPosition.x - patchSize * sin( clipAngle  ));
		int eyeY = cast(int)(viewPosition.z + patchSize * cos( clipAngle  ));

		int leftX  = cast(int)(eyeX + 100.0f * sin( (clipAngle-(fov/1.8 * pi/180.0f ))));
		int leftY  = cast(int)(eyeY - 100.0f * cos( (clipAngle-(fov/1.8 * pi/180.0f ))));

		int rightX = cast(int)(eyeX + 100.0f * sin( (clipAngle+(fov/1.8 * pi/180.0f ))));
		int rightY = cast(int)(eyeY - 100.0f * cos( (clipAngle+(fov/1.8 * pi/180.0f ))));

		triPool.nextTriNode = 0;
		trisRendered = 0;

		for(int y = 0; y < patchesPerSide; y++)
			for(int x = 0; x < patchesPerSide; x++){
				Patch patch;
				patch = patches[y][x];
				assert(patch !is null);
				patch.reset();
				patch.setVisibility(eyeX, eyeY, leftX, leftY, rightX, rightY);
				if(patch.varianceDirty)
					patch.computeVariance();
				if(patch.visible){
					if(x > 0)
						patch.baseLeft.leftNeighbor = &patches[y][x-1].baseRight;
					else
						patch.baseLeft.leftNeighbor = null;

					if(x < patchesPerSide-1)
						patch.baseRight.leftNeighbor = &patches[y][x+1].baseLeft;
					else
						patch.baseRight.leftNeighbor = null;

					if(y > 0)
						patch.baseLeft.rightNeighbor = &patches[y-1][x].baseRight;
					else
						patch.baseLeft.rightNeighbor = null;

					if(y < patchesPerSide-1)
						patch.baseRight.rightNeighbor = &patches[y+1][x].baseLeft;
					else
						patch.baseRight.rightNeighbor = null;	
				}
			}
	}

	void loadTerrain(){
		if(gpgpuEnabled == false){
			heightMap = cast(ubyte*)malloc(mapSize * mapSize * ubyte.sizeof);
			memset(heightMap, 0, mapSize * mapSize * ubyte.sizeof);

			writefln("Generating map...");
			float p = 0.1;
			double w;
			for(int x = 0; x < mapSize; x++)
				for(int z = 0; z < mapSize; z++){
					w = (Perlin.fullNoise(cast(double)x, 0.f, cast(double)z, .01) + 1.)/2.0;
					assert(w >= 0.f);
					assert(w <= 1.f);
					heightMap[x + z * mapSize] = cast(ubyte)(w * 128.);
					if((z+x*mapSize)/cast(float)(mapSize*mapSize) > p){
						writefln(p*100);
						p += 0.1;
					}
				}
			writefln(100);
			writefln("Done.");
		}
		else{
			heightMap = cast(ubyte*)malloc(mapSize * mapSize * ubyte.sizeof);
			gpgpu.genTerrain(heightMap, mapSize);

		}

	}

	void tessellate(){
		Patch patch;

		for(int y = 0; y < patchesPerSide; y++)
			for(int x = 0; x < patchesPerSide; x++){
				patch = patches[y][x];
				if(patch.visible)
					patch.tessellate(viewPosition, frameVariance); // ??
			}

	}

	void render(){
		Patch patch;

		for(int y = 0; y < patchesPerSide; y++)
			for(int x = 0; x < patchesPerSide; x++){
				patch = patches[y][x];
				if(patch.visible)
					patch.render();
			}

		if(triPool.nextTriNode != desiredTris)
			frameVariance += cast(float)(triPool.nextTriNode-desiredTris)/cast(float)desiredTris;
		if(frameVariance < 0)
			frameVariance = 0;

//		writefln(triPool.nextTriNode, " ",frameVariance);
	}

	ubyte height(float x, float z){
		return heightMap[cast(int)x + cast(int)z * mapSize];
	}


	private{
		int		mapSize;
		int		patchesPerSide;
		int		patchSize;
		int		varianceDepth;
		float	fov = 90.f;

		float	frameVariance = 300.0f;
		int		desiredTris = 10000;

		TriPool	triPool;

		ubyte*	heightMap;
		Patch	patches[][];

		int		trisRendered;
	}
	public{
		vec3	viewPosition;
		float	clipAngle = .0f;
		bool	gpgpuEnabled = false;
	}
}
